///\todo upgrade

//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2012 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#ifndef _BUOLA_IO_CPACKETSTREAM_H_
#define _BUOLA_IO_CPACKETSTREAM_H_

#include <buola/io/csocket.h>

namespace buola { namespace io {

template<typename tPacket>
class CPacketStream
{
public:
    CPacketStream()
        :   mBuffer(nullptr)
    {}
    
    CPacketStream(io::PSocket pSocket)
        :   mBuffer(alloc_array<char>(tPacket::cMaxPacketSize))
        ,   mDataLen(0)
        ,   mEOF(false)
        ,   mSocket(pSocket)
    {
        mBufferDataStart=mBuffer;
    }

    CPacketStream(const CPacketStream&)=delete;
    CPacketStream(CPacketStream &&pO)
        :   mBuffer(pO.mBuffer)
        ,   mBufferDataStart(pO.mBufferDataStart)
        ,   mDataLen(pO.mDataLen)
        ,   mEOF(pO.mEOF)
        ,   mSocket(pO.mSocket)
    {        
        pO.mBuffer=nullptr;
    }
    
    CPacketStream &operator=(const CPacketStream&)=delete;
    CPacketStream &operator=(CPacketStream &&pO)
    {
        using std::swap;
        swap(mBuffer,pO.mBuffer);
        swap(mBufferDataStart,pO.mBufferDataStart);
        swap(mDataLen,pO.mDataLen);
        swap(mEOF,pO.mEOF);
        swap(mSocket,pO.mSocket);
    }
    
    ~CPacketReader()
    {
        free(mBuffer);
    }

private:
    ///checks if there is enough data for a packet in the buffer (used for non-packet oriented sockets)
    std::unique_ptr<tPacket> CheckPacket()
    {
        if(mDataLen<tPacket::cHeaderSize) return nullptr;
        
        //GetSizeFromHeader must return the length of the packet, including the header
        std::size_t lPacketSize=tPacket::GetSizeFromHeader(mBufferDataStart);
        if(lPacketSize>mDataLen) //not enough data yet
        {
            if(lPacketSize>tPacket::cMaxPacketSize)
            {
                throw XRange("packet size is longer than maximum allowed");
            }
            return nullptr;
        }

        std::unique_ptr<tPacket> lPacket(new tPacket(mBufferDataStart,lPacketSize));
        if(mPassedSocket)
            lPacket->SetPassSocket(std::move(mPassedSocket));
        
        mDataLen-=lPacketSize;
        mBufferDataStart+=lPacketSize;
        //we don't rewind the buffer now because the packet we return points to internal memory
        //it will be rewound before reading again
        
        return lPacket;
    }

public:
    std::unique_ptr<tPacket> Receive(bool pBlock=false)
    {
        if(mSocket->SocketType()&CSocket::TYPE_PACKET) //easy case
        {
            msg_info() << "easy case!\n";
            io::PSocket lSocket;
            std::size_t lToRead=tPacket::cMaxPacketSize;
            ssize_t lRead=pBlock?mSocket->ReceivePacketWithSocket(mBuffer,lToRead,lSocket)
                                :mSocket->ReceivePacketWithSocketNB(mBuffer,lToRead,lSocket);
            
            if(lRead<0) return nullptr; //WOULDBLOCK
            if(lRead==0)
            {
                mEOF=true;
                return nullptr;
            }

            if(lRead<tPacket::cHeaderSize)
            {
                msg_warn() << "packet too short!\n";
                return nullptr;
            }
            
            std::unique_ptr<tPacket> lPacket(new tPacket(mBuffer,lRead));
            if(lSocket)
                lPacket->SetPassSocket(std::move(lSocket));
            return lPacket;
        }
        else
        {
            msg_info() << "hard case!\n";
            //if buffer data start is at the beginning, it is impossible that we have a new packet
            if(mBufferDataStart!=mBuffer)
            {
                if(mDataLen)
                {
                    if(std::unique_ptr<tPacket> lPacket=CheckPacket())
                        return lPacket;
                    memmove(mBuffer,mBufferDataStart,mDataLen);
                }
                mBufferDataStart=mBuffer;
            }

            io::PSocket lSocket;
            std::size_t lToRead=tPacket::cMaxPacketSize-mDataLen;
            ssize_t lRead=pBlock?mSocket->ReceivePacketWithSocket(mBuffer+mDataLen,lToRead,lSocket)
                                :mSocket->ReceivePacketWithSocketNB(mBuffer+mDataLen,lToRead,lSocket);
            if(lSocket)
            {
                mPassedSocket=std::move(lSocket);
            }

            if(lRead<0) return nullptr; //WOULDBLOCK
            if(lRead==0)
            {
                mEOF=true;
                return nullptr;
            }

            mDataLen+=lRead;

            return CheckPacket();
        }
    }
    
    void Send(const tPacket &pPacket)
    {
        if(pPacket.PassSocket())
            mSocket->SendPacketWithSocket(pPacket.Data(),pPacket.Size(),pPacket.PassSocket().get());
        else
            mSocket->SendPacket(pPacket.Data(),pPacket.Size());
    }

    bool AtEOF()  {   return mEOF;    }

private:
    char *mBuffer;
    char *mBufferDataStart;
    io::PSocket mPassedSocket;
    int mDataLen;   //number of valid bytes
    bool mEOF;
    io::PSocket mSocket;
};

/*namespace io*/ } /*namespace buola*/ }

#endif
